/*
 * Copyright (c) 2016. 恒昌互联网运营中心.
 */
package com.credithc.signature.callback.configure.eagle;

import ch.qos.logback.core.helpers.ThrowableToStringArray;
import com.credithc.baseserv.core.exception.BsException;
import com.credithc.eagleeye.agent.factory.EagleEyeAgentFactory;
import com.credithc.eagleeye.agent.util.HostUtil;
import com.credithc.eagleeye.constants.RpcTypeEnum;
import com.credithc.eagleeye.thread.EagleEyeThreadContext;
import com.credithc.eagleeye.thread.TraceInfo;
import com.credithc.eagleeye.util.FastJsonConvert;
import com.credithc.eagleeye.vo.RpcTraceInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.time.FastDateFormat;
import org.apache.ibatis.javassist.ClassClassPath;
import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;
import org.apache.ibatis.javassist.CtMethod;
import org.apache.ibatis.javassist.Modifier;
import org.apache.ibatis.javassist.bytecode.CodeAttribute;
import org.apache.ibatis.javassist.bytecode.LocalVariableAttribute;
import org.apache.ibatis.javassist.bytecode.MethodInfo;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.dao.QueryTimeoutException;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;

/**
 * 描 述: 鹰眼WebService方法<br/>
 * 作 者: zhangjin<br/>
 * 创 建：2016年09月27日<br/>
 * 版 本：v1.0.0<br>
 * <p/>
 * 历 史: (版本) 作者 时间 注释 <br/>
 */
//@Aspect
//@Component
@Slf4j
public class ServiceAspectHandler {

    private static final FastDateFormat ISO_DATETIME_TIME_ZONE_FORMAT_WITH_MILLIS = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ");

    @Pointcut(value = "execution(* com.credithc.signature.callback.service.*.*(..))")
    public void servicePointcut() {
    }

    @Around(value = "servicePointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {

        TraceInfo traceInfo = EagleEyeThreadContext.getTraceInfo();
        if (traceInfo == null) {
            traceInfo = new TraceInfo();
        } else {
            traceInfo.addSequenceNo();
        }
        long beginTime = System.currentTimeMillis();
        Object resultObject = null;
        RpcTraceInfoVo rpcTraceInfoVo = new RpcTraceInfoVo();
        try {
            rpcTraceInfoVo.setRequestDateTime(ISO_DATETIME_TIME_ZONE_FORMAT_WITH_MILLIS.format(new Date()));
            rpcTraceInfoVo.setTraceId(traceInfo.getTraceId());
            rpcTraceInfoVo.setRpcId(traceInfo.getRpcId());
            rpcTraceInfoVo.setRpcType(RpcTypeEnum.HTTP.getRpcName());

            String simpleName = pjp.getTarget().getClass().getSimpleName();//类名
            MethodSignature signature = (MethodSignature) pjp.getSignature();
            Method method = signature.getMethod();

            rpcTraceInfoVo.setServiceCategory("Service");
            rpcTraceInfoVo.setServiceName(simpleName);
            rpcTraceInfoVo.setMethodName(method.getName());

            //获取方法参数名称
            String[] paramNames = getFieldsName(pjp.getTarget().getClass().getName(), pjp.getSignature().getName());
            //获取方法参数数组
            Object[] method_args = pjp.getArgs();
            HashMap paramMap = buildParams(paramNames, method_args);
            rpcTraceInfoVo.setRequestJson(FastJsonConvert.convertObjectToJSON(paramMap));
            rpcTraceInfoVo.setUserInfo(traceInfo.getUserInfo());
            rpcTraceInfoVo.setServerHost(HostUtil.getIp());

            //执行调用
            resultObject = pjp.proceed();

            rpcTraceInfoVo.setRunTime(System.currentTimeMillis() - beginTime);
            rpcTraceInfoVo.setResult("OK");
            rpcTraceInfoVo.setResponseJson(FastJsonConvert.convertObjectToJSON(resultObject));
            //恢复原层级
            traceInfo.setTraceId(rpcTraceInfoVo.getTraceId());
            traceInfo.setRpcId(rpcTraceInfoVo.getRpcId());
        } catch (Exception ex) {
            if (ex instanceof BsException) {
                rpcTraceInfoVo.setResult("OK");
            }else if (ex instanceof QueryTimeoutException){
                rpcTraceInfoVo.setResult("OK");
            }else {
                rpcTraceInfoVo.setResult("ERROR");
            }
            String[] traceStr = ThrowableToStringArray.convert(ex);
            StringBuilder builder = new StringBuilder();
            String[] var13 = traceStr;
            int var14 = traceStr.length;
            for (int var15 = 0; var15 < var14; ++var15) {
                String trace = var13[var15];
                builder.append(trace);
                builder.append("\n");
            }
            rpcTraceInfoVo.setResponseJson(builder.toString());
            throw ex;
        } finally {
            EagleEyeAgentFactory.getInstance().send2EagleEye(rpcTraceInfoVo);
        }

        return resultObject;
    }

    /**
     * 使用javassist来获取方法参数名称
     *
     * @param class_name  类名
     * @param method_name 方法名
     * @return
     * @throws Exception
     */
    private String[] getFieldsName(String class_name, String method_name) throws Exception {
        Class<?> clazz = Class.forName(class_name);
        String clazz_name = clazz.getName();
        ClassPool pool = ClassPool.getDefault();
        ClassClassPath classPath = new ClassClassPath(clazz);
        pool.insertClassPath(classPath);

        CtClass ctClass = pool.get(clazz_name);
        CtMethod ctMethod = ctClass.getDeclaredMethod(method_name);
        MethodInfo methodInfo = ctMethod.getMethodInfo();
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
        if (attr == null) {
            return null;
        }
        String[] paramsArgsName = new String[ctMethod.getParameterTypes().length];
        int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;
        for (int i = 0; i < paramsArgsName.length; i++) {
            paramsArgsName[i] = attr.variableName(i + pos);
        }
        return paramsArgsName;
    }

    /**
     * 打印方法参数值  基本类型直接打印，非基本类型需要重写toString方法
     *
     * @param paramsArgsName  方法参数名数组
     * @param paramsArgsValue 方法参数值数组
     */
    private HashMap buildParams(String[] paramsArgsName, Object[] paramsArgsValue) {
        if (ArrayUtils.isEmpty(paramsArgsName) || ArrayUtils.isEmpty(paramsArgsValue)) {
            log.info("该方法没有参数");
            return null;
        }
        HashMap<String, Object> paramMap = new HashMap();
        for (int i = 0; i < paramsArgsName.length; i++) {
            //参数名
            String name = paramsArgsName[i];
            //参数值
            Object value = paramsArgsValue[i];
            String ret = null;
            if(value!=null){
                ret = value.toString();
            }
            paramMap.put(name, ret);
        }
        return paramMap;
    }
}
